﻿using System;
using NMock2.Internal;

namespace NMock2
{
	/// <summary>
	/// Tracks the expectations created by its <see cref="Mock{T}"/>s and <see cref="Stub{T}"/>s.
	/// <para><span style="color:red;font-weight:bold;">This class is new to NMock3.</span></para>
	/// </summary>
	/// <remarks>
	/// The <b>MockFactory</b> is a main component of the NMock3 API.  It is used to create <see cref="Mock{T}"/>
	/// instances and <see cref="Stub{T}"/> instances of a <see langword="class"/> or <see langword="interface" />.
	/// </remarks>
	/// <seealso cref="Mockery"/>
	public class MockFactory : Mockery
	{
		#region Constructors
		/// <summary>
		/// Creates a default <see cref="MockFactory"/>.
		/// </summary>
		/// <remarks>
		/// Default <see cref="MockFactory"/> classes do not ignore unexpected incovations.
		/// </remarks>
		public MockFactory()
		{
			
		}

		/// <summary>
		/// Creates a <see cref="MockFactory"/> that allows ignoring of unexpected invocations.
		/// </summary>
		/// <param name="ignoreUnexpectedInvocations">A value to set the <see cref="IgnoreUnexpectedInvocations"/> property.</param>
		/// <remarks>
		/// Use this constructor when you want to ingore unexpected invocations.
		/// </remarks>
		public MockFactory(bool ignoreUnexpectedInvocations)
		{
			IgnoreUnexpectedInvocations = ignoreUnexpectedInvocations;
		}
		#endregion

		#region CreateMock

		/// <summary>
		/// Creates a <see cref="Mock{T}"/> with all of the default values.
		/// <para><span style="color:red;font-weight:bold;">This method is new to NMock3.</span></para>
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		public Mock<T> CreateMock<T>() where T : class
		{
			return CreateMock<T>(new MyMockBuilder().DefaultNameFor(typeof (T)));
		}

		/// <summary>
		/// Creates a <see cref="Mock{T}"/> with the specified name.
		/// <para><span style="color:red;font-weight:bold;">This method is new to NMock3.</span></para>
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="name"></param>
		/// <returns></returns>
		public Mock<T> CreateMock<T>(string name) where T : class
		{
			return CreateMock<T>(name, MockStyle.Default);
		}

		/// <summary>
		/// Creates a <see cref="Mock{T}"/> with the specified constructor arguments.
		/// <para><span style="color:red;font-weight:bold;">This method is new to NMock3.</span></para>
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="args"></param>
		/// <returns></returns>
		public Mock<T> CreateMock<T>(params object[] args) where T : class
		{
			return CreateMock<T>(new MyMockBuilder().DefaultNameFor(typeof (T)), MockStyle.Default, args);
		}

		/// <summary>
		/// Creates a <see cref="Mock{T}"/> with the specified <see cref="MockStyle"/>.
		/// <para><span style="color:red;font-weight:bold;">This method is new to NMock3.</span></para>
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="mockStyle"></param>
		/// <returns></returns>
		public Mock<T> CreateMock<T>(MockStyle mockStyle) where T : class
		{
			return CreateMock<T>(new MyMockBuilder().DefaultNameFor(typeof(T)), mockStyle);
		}

		/// <summary>
		/// Creates a <see cref="Mock{T}"/> with the specified name and <see cref="MockStyle"/>.
		/// <para><span style="color:red;font-weight:bold;">This method is new to NMock3.</span></para>
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="name"></param>
		/// <param name="mockStyle"></param>
		/// <returns></returns>
		public Mock<T> CreateMock<T>(string name, MockStyle mockStyle) where T : class
		{
			return CreateMock<T>(name, mockStyle, new object[0]);
		}

		/// <summary>
		/// Creates a <see cref="Mock{T}"/> with the specified name, <see cref="MockStyle"/>, and constructor arguments.
		/// <para><span style="color:red;font-weight:bold;">This method is new to NMock3.</span></para>
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="name"></param>
		/// <param name="mockStyle"></param>
		/// <param name="args"></param>
		/// <returns></returns>
		public Mock<T> CreateMock<T>(string name, MockStyle mockStyle, params object[] args) where T : class
		{
			return new Mock<T>(NewMock(typeof(T), DefinedAs.Named(name).OfStyle(mockStyle).WithArgs(args)));
		}

		public Mock<T> CreateMock<T>(params Type[] additionalInterfaceImplementations) where T : class
		{
			return new Mock<T>(NewMock(typeof(T), DefinedAs.Named(new MyMockBuilder().DefaultNameFor(typeof(T))).OfStyle(MockStyle.Default).Implementing(additionalInterfaceImplementations)));
		}

		#endregion

		#region CreateStub

		/// <summary>
		/// Creates a <see cref="Stub{T}"/> for the specified class or interface.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		public Stub<T> CreateStub<T>() where T : class
		{
			return new Stub<T>(NewMock(typeof(T), DefinedAs.Named(new MyMockBuilder().DefaultNameFor(typeof(T))).OfStyle(MockStyle.Stub).WithArgs(new object[0])));
		}

		#endregion

		/// <summary>
		/// Verifies that all expectations have been met.
		/// Will be called in <see cref="Mockery.Dispose"/>, too. 
		/// </summary>
		public new void VerifyAllExpectationsHaveBeenMet()
		{
			if (_suppressExpectations)
			{
				_suppressExpectations = false;
				return;
			}
			base.VerifyAllExpectationsHaveBeenMet();
		}

		private bool _suppressExpectations;

		/// <summary>
		/// Suppresses when the factory would throw an exception on unmet or unexpected expectations until the next time
		/// VerifyAllExpectationsHaveBeenMet() is called.
		/// </summary>
		public void SuppressUnexpectedAndUnmetExpectations()
		{
			_suppressExpectations = true;
		}

		/// <summary>
		/// Gets or sets a value that indicates if the factory should throw an exception immediately
		/// when a unexpected invocation occurs.
		/// </summary>
		public bool IgnoreUnexpectedInvocations { get; set; }

		private class MyMockBuilder : MockBuilder
		{
			public new string DefaultNameFor(Type type)
			{
				return base.DefaultNameFor(type);
			}
		}
	}
}